perm filename WCCF.4[P,JRA] blob
sn#556271 filedate 1981-01-12 generic text, type C, neo UTF8
COMMENT ā VALID 00002 PAGES
C REC PAGE DESCRIPTION
C00001 00001
C00002 00002 GUIDELINES FOR CHOOSING AN OBJECT-ORIENTED PROGRAMMING STYLE IN LISP
C00046 ENDMK
Cā;
GUIDELINES FOR CHOOSING AN OBJECT-ORIENTED PROGRAMMING STYLE IN LISP
Jim Schmolze
Bolt Beranek and Newman Inc.
Cambridge, Massachusetts
Abstract: Given a "hybrid" Lisp system which
includes an object-oriented programming
facility, some guidelines are presented for
choosing the object-oriented style over
"pure Lisp". These guidelines are presented
in the context of a particular object-
oriented programming system, called
TINYTALK, which is briefly described.
1. Should you read this paper?
This paper is written from the perspective of
a programmer or system designer who is about to
design and construct a Lisp program. Its intent
is to provide useful guidelines for deciding
when an object-oriented design is preferable to
"pure Lisp". The quotations just used are simply
a reminder that the various Lisp languages
available today differ immensely, making "pure
Lisp" a vague term. It will be used in this
paper to include all of Lisp except for object-
oriented facilities.
An implicit assumption of this paper is that a
Lisp system with an object-oriented programming
facility is available to the designer, making it
possible to produce either Lisp programs,
object-oriented programs or a hybrid of the two.
Of course, you may not have an object-oriented
facility in your Lisp, but maybe someday ...
Before we continue, let us simplify some of
the notation. The word "object" will replace
1
"object-oriented programming" and "Lisp-object"
2
will replace "Lisp object-oriented programming".
Prior to presenting these guidelines, a brief
description of a particular Lisp-object system
will be given. The author built this system,
called TINYTALK, for the variant of Lisp called
INTERLISP [Teitelman 78]. TINYTALK will provide
a useful context for the arguments contained in
this paper.
The advice offered in this paper arises from
the author's experience with designing and
writing several programs in Lisp-object
environments over the course of a year, after
several years of working in "pure Lisp"
environments. The hybrid environments were
INTERLISP with TINYTALK and MACLISP [Moon 74]
with the EXTEND package [Kerns 80].
2. A brief description of TINYTALK
The original goal for TINYTALK was to provide
a simple test-bed for writing Lisp-object
programs, hence it is still incomplete. However,
it does include several features found among
other object systems. The primary influences
were SMALLTALK [Goldberg&Kay 76, Ingalls 78] and
the EXTEND package of MACLISP.
The basic structure of TINYTALK allows:
o definition of classes
o creation of instances of a class and
access to instance variables
o applying operators to an instance,
where each operator must be defined for
the class of the instance
The programmer creates a hierarchy of classes
which form an acyclic graph. Every class has
one or more parent class. The system is
initialized with a distinguished class, called
"Thing". If no parent is explicitly stated for a
class, its parent is the class "Thing". Hence,
the Thing class is the root class of the
hierarchy, where Thing has no parent class.
The hierarchy of classes is an important
design consideration because subordinate classes
inherit instance variable names and operator
definitions from their parents. The next section
will explain these terms in detail.
2.1. Defining classes
A class definition consists of:
o a name -- which is merely a convenient
device for referring to a class.
o a set of parent classes -- this forms
the class hierarchy.
o a set of locally defined instance
variable names -- each instance of a
class will have a slot corresponding to
each variable name in the class
definition. These slots are able to
store any Lisp value. The complete set
of instance variable names for a class
is the union of its locally defined
variable names plus those of all its
parents.
o a set of locally defined operator
definitions -- the inheritance of
operators is similar to that of
instance variable names. If a local
operator definition and an inherited
one have the same name, the local one
overrides. This allows operations to be
redefined for children classes.
Additionally, if two parent classes
have operators with the same name, the
child class inherits the definition
from the "first" parent class, where
the order of the parent classes is a
part of declaring the parents.
Each operator definition is stored as a Lisp
function. Given the pair, <class,operator-name>,
a Lisp function name is generated and the
definition is stored there. Each definition can
have any number of arguments with the
requirement that the first argument be the
instance to which the operation is being
applied.
2.2. Example: A Window Package
An example, which will now be introduced, will
be used throughout this paper. It is part of a
program the author wrote using INTERLISP and
TINYTALK. The window package is a simple
program for manipulating terminals which have a
3
window capability. It allows a user to define
an arbitrary number of windows and activate them
whenever desired for printing purposes. An
excellent example of window usage is
DLISP [Teitelman 77], which provides an
extremely sophisticated window package.
Part of the class hierarchy for the window
package is:
Thing -- vars: <none>
ā ops: (PrintVars self)
|
Window -- vars: X0,Y0,Width,Height,Color
ā ops: (Move self x0 y0)
| (Grow self width height)
| (Activate self)
| (Clear self)
| (Draw self)
|
WorkWindow -- vars: Title,TitleWindow,
TextWindow
ops: (Activate self)
(Clear self)
(Draw self)
(BlinkTitle self)
The class "Thing" is defined by the system
with no instance variables and one operator
which simply prints the values of all variables
in an instance. Hence, all classes inherit the
operator "PrintVars". Notice that each operator
is listed with the arguments it expects, the
first of which is always the instance to which
the operator is being applied.
The class "Window" is a child of Thing and has
5 instance variables for its origin (X0 and Y0),
size (Width and Height) and color. The "Move"
and "Grow" operators simply modify the values of
some of these variables. "Activate" will cause
all terminal output to be printed in the given
window. "Draw" will completely draw the window
by "Activate"-ing it and "Clear"-ing it.
The class "WorkWindow" is intended to be a
window with a title on top. The variable
"Title" holds the text of the title, which is
printed inside the window stored in
"TitleWindow". "TextWindow" stores the window in
which normal output appears. Notice that an
instance of WorkWindow is also a window, namely
it inherits an X0, Y0, etc. Also it inherits the
operator definitions for "Grow" and "Move".
However, it redefines the operators "Activate",
"Clear" and "Draw" since they differ for
WorkWindows from other windows (i.e. "Draw"
must print the title, etc.). The operator
"BlinkTitle" is added to WorkWindows. Such an
operator doesn't make sense for other windows
since they don't have titles.
2.3. Creating Instances and Accessing their
Variables
Instances of any class may be created with the
Lisp function
(CreateInst class)
which returns a new instance with all variables
initialized to NIL. The instance contains
storage slots for all instance variable names
associated with the class. The values of
instance variables can be retrieved via
(getv instance var-name)
and can be set via
(setv instance var-name value)
Using the Window class example, let w be an
instance of the Window class.
4
(setv w 'X0 10) returns 10, after which
(getv w 'X0) also returns 10
The access functions for instance variables
could just as well have been done by
automatically adding operators for each variable
when the class was defined. The choice of using
getv and setv was made simply because they were
easy to implement. They do not constitute a
permanent part of the language.
2.4. Applying Operators to Instances
An operator is applied to an instance via
(send instance op-name . arguments)
The class of the the instance together with
5
the op-name maps directly into a Lisp function
(see section on Defining Classes). This function
is then called as
(fn instance . arguments)
The value of the "send" is the value returned by
the evaluation of the operator definition.
Using our example, once again, if w is an
instance of the Window class:
(send w 'Move 10 20) is evaluated as
(Move-fn-for-Window w 10 20)
which will set the X0 of w to 10 and its Y0 to
20.
This concludes the brief overview of TINYTALK.
3. Guidelines for Choosing Object-Oriented
Programming Style
Before launching into guidelines, a basis of
programming methodology will be presented. The
following comments about the design process for
a software system are certainly not provably
correct. However, they constitute the author's
(and others) combination of structured
programming techniques and common sense.
3.1. The Intentional Design of a Software System
The title of this section may have rather
esoteric "ring" to it, however, a simple meaning
is intended. First, let us assume that we wish
to design a program which has a reasonably well
specified behavior. During the design process,
several design goals will necessarily be
achieved, whether the program is to be written
in Lisp, Lisp-object, PASCAL, FORTRAN or any
other language. Specifically, these goals are:
1. Design of the intent of the data
6
structures required for the program.
2. Design of the relationships between
these data structures.
3. Design of the intent of operations to
be performed upon these data
structures and the algorithms needed
to define them.
Of course, these design goals may not be
achieved for the entire program simultaneously.
In fact, the author's experience shows that the
design of a system grows gradually, changing as
the system is constructed. However, for the
purposes of the following discussion, we will
assume that the intentional design, which has
just been described, has been completed for the
program being planned.
3.2. Guideline 1: Counting unique data
structures and operations
Two questions are relevant at this point:
Q1: What are the unique types of data
structures in the design?
Q2: What are the unique types of
operations for all data structures?
Given the fact that data structures are easily
modeled by class instances, one is tempted to
think that if there are lots of different data
structures, then object programming is
preferrable. Also, since operator definitions
are similar to Lisp functions, if there are lots
of different operators, then pure Lisp seems
more appropriate. Unfortunately, the mere totals
of data structure types and operators offer no
real guidance at all because both of them are
easily defined in either object or Lisp systems.
In fact, the answers to these questions do not
provide obvious guidelines at all, but they do
provide a useful framework for the next section.
3.3. Guideline 2: Similar operations for
different data structures
Q3: How often do several data
structures have similar or identical
operators?
Q4: Of these operators, which have
identical algorithms for all the
data structures which use them?
The answers to these questions determine the
amount of "operator overlap" that occurs among
the data structures. Q3 is concerned with the
overlap of the intention of operators; Q4
addresses the overlap of definitionally
equivalent operators.
In the window package example, the set of
operators for both Window and WorkWindow are
nearly the same. However, for some of them, such
as "Draw", the actual code to perform the
operation will differ for each class while
operators such as "Move" are programmed
identically for both.
If there is much operator overlap, there are
two reasons for choosing object design. First,
in the object design, the programmer needs to
use only one name for the operator whereas in
pure Lisp, a different name (i.e. a different
Lisp function) is needed for each variant that
must be coded differently. Using our example
again, the "Draw" operation for Windows is coded
differently than "Draw" for WorkWindows, but the
operator "Draw" works for both of them. In pure
Lisp, two names (i.e. functions) are needed,
such as, "DrawWindow" and "DrawWorkWindow".
Second, in object design, the part of the
software that calls for an operation, such as
"Draw", does not need to know whether the window
is a normal Window or a WorkWindow because the
system automatically uses the appropriate
definition from the class of the instance. In
pure Lisp, one must determine the type of the
data structure so the correct draw function can
be called.
The second argument presented here is actually
a general feature of object systems. However,
this feature is especially useful when there is
much operator overlap. An important disclaimer
to note is that if the actual definition for an
operator is identical for each type of data
structure, these two arguments are not
applicable and this guideline becomes useless.
In that situation, only one function is needed
in pure Lisp to perform the operation and it
could be used for all of the types of data
structures.
It must also be noted that no claim is being
made about the total amount of software required
to write programs for which this guideline
applies. In fact, in the author's opinion, the
program's size for either pure Lisp or Lisp-
object systems is approximately the same. The
critical difference is with respect to the ease
and simplicity of designing, writing and
debugging software.
3.4. Guideline 3: Utilizing the inheritance
between classes
Rather than enumerating the proper questions
that should be asked, we will examine the
properties of inheritance to discover criteria
for determining its utility for any particular
program. The thrust of this guideline is to be
able to recognize program designs for which the
class inheritance of an object system offers
distinct advantages over pure Lisp.
The first step requires an examination of the
relationships between data structures in our
intentional design. In particular, we look for
relationships that fit well into the paradigm of
a class hierarchy for object systems. There are
two properties to seek. The easiest is one of
subsumption; i.e. is one data structure a more
specific version of another where the size of
the data structure is simply extended and not
modified. This type of relationship forms the
parent-child class relationship. The second type
is the composition of two or more data
structures into a new structure, possibly with
extensions. This type of relationship also forms
a parent-child relationship but with multiple
parents.
An important item to remember when performing
this exercise is that inheritance works for both
structure and operators, so they must be
considered together. The analysis performed for
the previous guideline will be useful here. If
there is "operator overlap" between classes that
7
have an ancestor relationship and if the
operators which overlap are defined identically,
then by placing the operator definition at the
8
"higher" class, the ancestor will inherit the
operator and its definition. The more of these
situations one finds, the better the case for
choosing object design.
3.5. Guideline 4: Do you "like" object-oriented
programming?
The question of "liking" the object style is
probably the crucial question for deciding
between object style versus pure Lisp. Of
course, this decision is intended to be made
with respect to the particular program being
designed. But what is meant by "liking"? The
point of the question is to get the designer to
consider whether the object style is
appropriate. Is it a comfortable and useful
paradigm for the design? Furthermore, which
portions of the program are best done with
object style and which are well suited for pure
Lisp?
Unfortunately, the only way to be able to
answer such subjective questions is to have had
experience with both types of environments. The
next section of this paper will discuss some
general advantages of object design and will
provide some information for making the choice.
However, it is the author's conviction that
experience is necessary to make the proper
choice.
4. General Advantages of Object-Oriented
Programming over Lisp (plus a tiny little
disadvantage)
Most of the advantages presented here deal
with aiding the designer and programmer in the
organization of the program. First of all, an
object system provides a partitioning between
data structures, along with their operators.
Each type of data structure and the operations
that can be performed upon it are automatically
grouped together by the system, providing a
useful tool for viewing the modular components
of the program.
Additionally, the intentional design of the
program, described earlier in the paper, has a
partial physical reification in the program.
Data structures and their operators plus some of
the relationships between structures are
explicitly represented in the actual program.
This allows the design to be reflected in the
program more completely than could be done in
pure Lisp.
A small advantage of object systems over some
variants of Lisp is that when one defines a data
structure in an object system (i.e. a class),
the system automatically provides functions for
creating, accessing and modifying instances of
the data structure. In TINYTALK, these system
functions are, respectively, "CreateInst",
"getv" and "setv"; all of which can be used for
any class. Hence, the programmer need not define
such functions for each type of data structure.
Some versions of Lisp, such as INTERLISP,
9
already have such facilities.
More importantly, the object system forces the
programmer into the philosophy of creating,
accessing and modifying instances through a
functional interface that is independent of the
actual structure of the instance. Instance
variables can only be accessed by name. For the
reader who is familiar with database systems,
this is roughly analogous to creating a data
model which resides between the system being
built and the physical structure of the data.
This particular type of modularity can also be
achieved in pure Lisp, but only when the
programmer chooses to do so.
The final advantage to be mentioned deals with
insulating the instances of a class. There are
two ways to modify the state of an instance. One
is to apply some operation which modifies it;
the other is to change its instance variables.
It is possible to create an object system which
did not allow instance variable modification
from operators outside of the class definition.
TINYTALK does not have this restriction, but it
could be extended as such. Given such a
property, the state of all instances of a class
would be insulated from software not contained
in the class definition because all modification
must go through a class operator. Of course,
other software in the system could perform
operations incorrectly, thereby causing
difficulties. But the modularity provided by
such insulation prevents the problems due to
external interference with the state of
instances.
The tiny little disadvantage mentioned in this
section's title is, unfortunately, not so small.
Execution speed of object programs is slower
than for pure Lisp. Each application of an
operator requires an examination of the instance
to find its class; a table-lookup in the class
to find the appropriate function for the
operator; and an application of the Lisp
function. In pure Lisp, only the actual
function call is necessary. Second, when a
portion of code which applies an operator is
10
compiled, it can never be compiled in-line
because the actual code to be executed cannot be
determined until execution time. In pure Lisp,
the actual code to be executed can often be
determined at compilation time, allowing in-line
compilation when desired.
Similar costs are incurred for each instance
variable access, since the access function
depends upon the class of the instance as well
as the variable name. The author has no
statistics upon the loss in execution time but
feels that they are small compared to other
gains offered by object systems.
5. Conclusions
Since the bulk of this paper is concerned with
summarizing the author's intuitions about Lisp-
object programming, it would be inappropriate to
include factual information in the conclusion.
Hence, let us briefly state what is probably
obvious by now.
No claim is made about the intrinsic value of
the object-oriented programming style. Nor is
any claim made about any particular programming
language paradigm. Instead, the author claims
that object-oriented programming makes system
design and implementation easier for some
classes of tasks. The guidelines presented are
intended to aide a system designer in deciding
when his particular program fits into such a
class.
Once again, the "ease" of design and
implementation just mentioned refers to reducing
the conceptual complexity of the system. For the
author, this type of reduction has the effect of
reducing the number of hours spent designing and
debugging a program.
Finally, although the context of this paper is
to contrast Lisp programming with and without an
object-oriented facility, nothing presented
depends critically upon the Lisp language,
except one. Namely, Lisp allows the introduction
of an object system and, furthermore, works well
with a hybrid environment. These arguments and
guidelines may well be appropriate for other
computer languages offering an object-oriented
facility.
FOOTNOTES
1
Note that I have resisted using the acronym
OOP.
2
Yes, you guessed it -- it could have been
LOOP.
3
A window capability on a terminal allows an
arbitrary rectangle to become the region in
which text is written and scrolled, just as if
it were the entire screen.
4
The X0 is quoted because setv evaluates its
arguments.
5
Unless the class does not have such an
operator, in which case a Lisp error occurs.
6
The usage of "data structure" is an attempt
to get away from language-specific terminology
such as "instance" or "list".
7
An ancestor relationship between classes
means there is a chain of parent-child class
relationships the connect the two given classes.
8
A "high" class is one that is close to the
root of the class hierarchy, namely, the Thing
class.
9
This facility is called the record package in
INTERLISP.
10
This is called open compilation in
INTERLISP.
REFERENCES
[Goldberg&Kay 76]
Goldberg, A., Kay, A.
SMALLTALK-72 Instruction Manual
Xerox Palo Alto Research Center,
Palo Alto, California, 1976.
[Ingalls 78] Ingalls, D.
The SMALLTALK-76 Programming
System Design and
Implementation.
In Proceedings of the Fifth
Annual ACM Symposium on
Principles of Programming
Languages. ACM, Tucson,
Arizona, January 23-25, 1978.
[Kerns 80] Kerns, R. W.
EXTEND package for MACLISP.
Massachusetts Institute of
Technology Artificial
Intelligence Laboratory.
Personal Communication.
[Moon 74] Moon, D.
MACLISP Reference Manual
Revision 0 April 1974 edition,
Project MAC, Massachusetts
Institute of Technology,
Cambridge, Massachusetts,
1974.
[Teitelman 77] Teitelman, W.
A Display Oriented Programmer's
Assistant.
In Proceedings of the Fifth
International Joint Conference
on Artificial Intelligence,
pages 905-915. IJCAI,
Cambridge, Massachusetts,
1977.
[Teitelman 78] Teitelman, W., Goodwin, J.W.,
Hartley, A.K., Lewis, D.C.,
Vittal, J.J., Yonke, M.D.,
Bobrow, D.G., Kaplan, R.M.,
Masinter, L.M., Sheil, B.A.
INTERLISP Reference Manual
Revised October 1978 edition,
Xerox Palo Alto Research
Center, Palo Alto, California,
1978.
-------